using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Web;
using WCSFContrib.Extensions.EventBroker;

namespace WCSFContrib.Extensions.CompositeWeb
{
	/// <summary>
	/// Implements the <see cref="IRequestEventBroker"/> interface.
	/// </summary>
	public class RequestEventBroker : IRequestEventBroker
	{
		private EventBrokingMemberList<EventInfo> publishersByTopic;
		private EventBrokingMemberList<MethodInfo> subscribersByTopic;
		private List<DelegateInfo> delegateInfos;

		private RequestEventBroker()
		{
			publishersByTopic = new EventBrokingMemberList<EventInfo>();
			subscribersByTopic = new EventBrokingMemberList<MethodInfo>();
			delegateInfos = new List<DelegateInfo>();
		}

		#region Single instance Session stuff
		private const string EventBrokerSessionName = "MVPComposibility.ContactManager.Dotway.Practices.CompositeWeb.ObjectBuilder.EventBroker.SessionEventBroker.Instance";

		/// <summary>
		/// Instance is stored in Session.
		/// </summary>
		public static RequestEventBroker Instance
		{
			get
			{
				object instance = null;
				if (HttpContext.Current != null &&
					 HttpContext.Current.Session != null)
				{
					instance = HttpContext.Current.Session[EventBrokerSessionName];
				}
				return instance != null ? (RequestEventBroker)instance : null;
			}
			private set
			{
				HttpContext.Current.Session[EventBrokerSessionName] = value;
			}
		}

		/// <summary>
		/// Set a new instance of the broker in Session. (Dispose of the old if existing.)
		/// </summary>
		public static void ResetEventBroker()
		{
			RequestEventBroker session = Instance;
			if (session != null)
				session.Dispose();
			Instance = new RequestEventBroker();
		}
		#endregion

		public void AddPublisher(object instance, MemberReference<EventInfo> data)
		{
			Publisher publisher = DoAddPublisher(instance, data);

			List<DelegateInfo> delegates = new List<DelegateInfo>();

			List<EventBrokingData<MethodInfo>> subscribers;
			if (subscribersByTopic.TryGet(data.Topic, out subscribers))
				foreach (Subscriber subscriber in subscribers)
				{
					DelegateInfo di = HookUpPublisherSubscriber(publisher, subscriber);
					delegates.Add(di);
				}

			AddDelegateInfo(delegates);
		}

		public void AddSubscriber(object instance, MemberReference<MethodInfo> data)
		{
			Subscriber subscriber = DoAddSubscriber(instance, data);

			List<DelegateInfo> delegates = new List<DelegateInfo>();

			List<EventBrokingData<EventInfo>> publishers;
			if (publishersByTopic.TryGet(data.Topic, out publishers))
				foreach (Publisher publisher in publishers)
				{
					DelegateInfo di = HookUpPublisherSubscriber(publisher, subscriber);
					delegates.Add(di);
				}

			AddDelegateInfo(delegates);
		}

		public void Dispose()
		{
			foreach (DelegateInfo delegateInfo in delegateInfos)
			{
				delegateInfo.Dispose();
			}
		}

		private void AddDelegateInfo(IEnumerable<DelegateInfo> delegates)
		{
			delegateInfos.AddRange(delegates);
		}

		private Publisher DoAddPublisher(object instance, MemberReference<EventInfo> data)
		{
			Publisher publisher = new Publisher(instance, data);

			List<EventBrokingData<EventInfo>> item;
			if (publishersByTopic.TryGet(data.Topic, out item))
			{
				item.Add(publisher);
			}
			else
			{
				Publisher[] publishers = new Publisher[1] { publisher };
				item = new List<EventBrokingData<EventInfo>>(publishers);
				publishersByTopic.Add(item);
			}

			return publisher;
		}

		private Subscriber DoAddSubscriber(object instance, MemberReference<MethodInfo> data)
		{
			Subscriber subscriber = new Subscriber(instance, data);

			List<EventBrokingData<MethodInfo>> item;
			if (subscribersByTopic.TryGet(data.Topic, out item))
			{
				item.Add(subscriber);
			}
			else
			{
				Subscriber[] subscribers = new Subscriber[1] { subscriber };
				item = new List<EventBrokingData<MethodInfo>>(subscribers);
				subscribersByTopic.Add(item);
			}

			return subscriber;
		}

		private DelegateInfo HookUpPublisherSubscriber(Publisher publisher, Subscriber subscriber)
		{
			Delegate @delegate = subscriber.GetDelegate(publisher.MemberInfo.EventHandlerType);
			publisher.AddEventHandler(@delegate);
			return new DelegateInfo(@delegate, publisher);
		}

		private class DelegateInfo : IDisposable
		{
			private readonly Delegate @delegate;
			private readonly Publisher publisher;

			public DelegateInfo(Delegate @delegate, Publisher publisher)
			{
				this.@delegate = @delegate;
				this.publisher = publisher;
			}

			public void Dispose()
			{
				publisher.RemoveEventHandler(@delegate);
			}
		}

		private class EventBrokingMemberList<T> : KeyedCollection<string, List<EventBrokingData<T>>>
			where T : MemberInfo
		{
			protected override string GetKeyForItem(List<EventBrokingData<T>> item)
			{
				return item[0].Topic;
			}

			public bool TryGet(string key, out List<EventBrokingData<T>> data)
			{
				if (Contains(key))
				{
					data = this[key];
					return true;
				}
				else
				{
					data = null;
					return false;
				}
			}
		}
	}
}